package handle

import (
	"awesome-runner/src/config"
	"awesome-runner/src/logr"
	"awesome-runner/src/queue"
	"awesome-runner/src/sql"
	interactive "awesome-runner/src/ssh"
	"awesome-runner/types"
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"github.com/RichardKnop/machinery/v2/tasks"
	"github.com/ThomasRooney/gexpect"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_translations "github.com/go-playground/validator/v10/translations/zh"
	"github.com/golang-module/carbon"
	"github.com/kataras/iris/v12"
	"github.com/mitchellh/go-homedir"
	taskLogrus "github.com/sirupsen/logrus"
	"golang.org/x/crypto/ssh"
)

type CommitRepository struct {
	After       string `json:"id"`
	UserName    string `json:"author_name"`
	CommentsUrl string `json:"comments_url"`
}

type InternalDeploy struct {
	Symbol        string `json:"symbol"`
	ReleaseSymbol string `json:"release_symbol"`
	Name          string `json:"name"`
	Secret        string `json:"secret"`
	Path          string `json:"path"`
	Option        uint8  `json:"option"`
	User          string `json:"user"`
	Host          string `json:"host"`
	Port          int    `json:"port"`
	Pwd           string `json:"pwd,omitempty"`
	Token         string `json:"token"`
	ProjectId     int    `json:"project_id"`
	Repo          string `json:"repo"`
	Owner         string `json:"owner"`
}

type ReleaseBody struct {
	Branch  string `json:"branch"`
	Symbol  string `json:"symbol"`
	HtmlUrl string `json:"html_url"`
}

// 项目列表
func ProjList(ctx iris.Context) {
	var (
		page int = 1
		size int = 20
	)

	page, err := ctx.URLParamInt("current")
	if err != nil {
		page = 1
	}
	size, err = ctx.URLParamInt("pageSize")
	if err != nil {
		size = 20
	}

	var (
		list           []types.InternalDeploy
		data           []InternalDeploy
		total          int64
		internalDeploy types.InternalDeploy
	)

	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		defer wg.Done()
		sql.GetLiteInstance().Where("is_delete = ?", 0).Offset((page - 1) * size).Limit(size).Find(&list)
	}()
	go func() {
		defer wg.Done()
		sql.GetLiteInstance().Model(&internalDeploy).Where("is_delete = ?", 0).Count(&total)
	}()
	wg.Wait()

	for _, item := range list {
		var temp string
		if item.Option == 2 {
			temp = ""
		} else {
			temp = config.Cnf.Domain + "?symbol=" + item.Symbol
		}

		data = append(data, InternalDeploy{
			Symbol:        temp,
			ReleaseSymbol: item.Symbol,
			Name:          item.Name,
			Secret:        item.Secret,
			Path:          item.Path,
			Option:        item.Option,
			User:          item.Auth.User,
			Host:          item.Auth.Host,
			Port:          item.Auth.Port,
			Token:         item.Token,
			ProjectId:     item.ProjectId,
			Repo:          item.Repo,
			Owner:         item.Owner,
		})
	}

	ctx.JSON(map[string]interface{}{
		"current":  page,
		"data":     data,
		"pageSize": size,
		"success":  true,
		"total":    total,
	})
}

// 创建项目
func ProjCreate(ctx iris.Context) {
	var body InternalDeploy
	bodyByte, _ := ctx.GetBody()
	logr.JSON.Unmarshal(bodyByte, &body)

	uni := ut.New(zh.New())
	trans, _ := uni.GetTranslator("zh")
	validate := validator.New()

	zh_translations.RegisterDefaultTranslations(validate, trans)

	if body.Option == 1 && body.Secret == "" {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "自动化部署秘钥不能为空",
		})
		return
	}

	internalDeploy := &types.InternalDeploy{
		Symbol: logr.SnowFlakeId(),
		Secret: body.Secret,
		Path:   body.Path,
		Option: body.Option,
		Auth: types.Authentication{
			Scheme: 1,
			User:   body.User,
			Host:   body.Host,
			Port:   body.Port,
			Pwd:    body.Pwd,
		},
		IsDelete:  false,
		Name:      body.Name,
		Token:     body.Token,
		ProjectId: body.ProjectId,
		Repo:      body.Repo,
		Owner:     body.Owner,
	}

	err := validate.Struct(internalDeploy)
	if err != nil {
		errs := err.(validator.ValidationErrors)
		// from here you can create your own error messages in whatever language you wish
		ctx.JSON(types.Response{
			Code: 400,
			Message: func(errs validator.ValidationErrors) string {
				var re string
				for _, e := range errs.Translate(trans) {
					re = e
					break
				}
				return re
			}(errs),
		})
		return
	}

	if err := sql.GetLiteInstance().Create(&internalDeploy).Error; err != nil {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "fail",
		})
	} else {
		ctx.JSON(types.Response{
			Code:    200,
			Message: "success",
		})
	}
}

// 更新项目
func ProjUpdate(ctx iris.Context) {
	var body InternalDeploy
	bodyByte, _ := ctx.GetBody()
	logr.JSON.Unmarshal(bodyByte, &body)

	if body.Option == 1 && body.Secret == "" {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "自动化部署秘钥不能为空",
		})
		return
	}

	symbol := ctx.Params().Get("symbol")
	var ext types.InternalDeploy
	sql.GetLiteInstance().Where("symbol = ?", symbol).Take(&ext)
	if ext == (types.InternalDeploy{}) {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "Unknown symbol",
		})
	}

	uni := ut.New(zh.New())
	trans, _ := uni.GetTranslator("zh")
	validate := validator.New()

	zh_translations.RegisterDefaultTranslations(validate, trans)

	internalDeploy := &types.InternalDeploy{
		Symbol: symbol,
		Secret: body.Secret,
		Path:   body.Path,
		Option: body.Option,
		Auth: types.Authentication{
			Scheme: 1,
			User:   body.User,
			Host:   body.Host,
			Port:   body.Port,
			Pwd: func(pwd string) string {
				if pwd == "" {
					return ext.Auth.Pwd
				}
				return pwd
			}(body.Pwd),
		},
		IsDelete:  ext.IsDelete,
		Name:      body.Name,
		Token:     body.Token,
		ProjectId: body.ProjectId,
		Repo:      body.Repo,
		Owner:     body.Owner,
	}

	err := validate.Struct(internalDeploy)
	if err != nil {
		errs := err.(validator.ValidationErrors)
		// from here you can create your own error messages in whatever language you wish
		ctx.JSON(types.Response{
			Code: 400,
			Message: func(errs validator.ValidationErrors) string {
				var re string
				for _, e := range errs.Translate(trans) {
					re = e
					break
				}
				return re
			}(errs),
		})
		return
	}

	if err := sql.GetLiteInstance().Model(&internalDeploy).Updates(&internalDeploy).Error; err != nil {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "fail",
		})
	} else {
		ctx.JSON(types.Response{
			Code:    200,
			Message: "success",
		})
	}
}

// 删除项目
func ProjDelete(ctx iris.Context) {
	var internalDeploy = types.InternalDeploy{}
	symbol := ctx.Params().Get("symbol")

	if err := sql.GetLiteInstance().Model(&internalDeploy).Where("symbol = ?", symbol).Update("is_delete", true).Error; err != nil {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "fail",
		})
	} else {
		ctx.JSON(types.Response{
			Code:    200,
			Message: "success",
		})
	}
}

// 发布上线单
func TaskPublish(ctx iris.Context) {
	var (
		crypt         types.AbstractCrypt
		body          ReleaseBody
		internalDeloy types.InternalDeploy
	)

	bodyByte, _ := ctx.GetBody()
	logr.JSON.Unmarshal(bodyByte, &body)

	uni := ut.New(zh.New())
	trans, _ := uni.GetTranslator("zh")
	validate := validator.New()

	zh_translations.RegisterDefaultTranslations(validate, trans)

	if body.Symbol == "" || body.Branch == "" || body.HtmlUrl == "" {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "参数错误",
		})
		return
	}

	sql.GetLiteInstance().Where("symbol = ?", body.Symbol).Take(&internalDeloy)
	if internalDeloy == (types.InternalDeploy{}) {
		ctx.JSON(types.Response{
			Code:    400,
			Message: "Unknown symbol",
		})
		return
	}

	resArr, err, params := queryRepo(internalDeloy, body)
	if err != nil {
		ctx.JSON(types.Response{
			Code:    400,
			Message: err.Error(),
		})
		return
	}

	cryptDataConfig := types.CryptDataConfig{
		Symbol: body.Symbol,
		Message: types.Message{
			Ref:      fmt.Sprintf(`refs/heads/%s`, body.Branch),
			After:    strings.TrimSpace(string(resArr.After)),
			UserName: strings.TrimSpace(string(resArr.After)),
			Repository: types.Repository{
				ProjectId: internalDeloy.ProjectId,
				HtmlUrl:   body.HtmlUrl,
				SshUrl:    body.HtmlUrl,
			},
		},
		Headers: ctx.Request().Header,
		Project: types.Project{
			Secret: internalDeloy.Secret,
			Path:   internalDeloy.Path,
			Auth:   internalDeloy.Auth,
		},
		InternalDeloy: internalDeloy,
		Payload:       "",
	}

	htmlUrl := body.HtmlUrl
	if strings.Contains(htmlUrl, "github.com") {
		crypt = types.DiscoverGithubCrypt(cryptDataConfig)
	} else if strings.Contains(htmlUrl, "gitee.com") {
		crypt = types.DiscoverGiteeCrypt(cryptDataConfig)
	} else {
		crypt = types.DiscoverGitlabCrypt(cryptDataConfig)
	}

	delivered(ctx, crypt, body, params)
}

// 投递任务
func delivered(ctx iris.Context, crypt types.AbstractCrypt, body ReleaseBody, params types.TaskParams) {
	logr.Logrus.Printf("当前分支 %s", body.Branch)
	uuid := logr.SnowFlakeId()
	taskLog, _ := os.Create("runtime/task/" + uuid + ".log")
	defer taskLog.Close()

	taskLogrus.SetOutput(taskLog)

	env, _ := logr.JSON.Marshal(params.Environment)

	eta := time.Now().Add(time.Second * 3)
	args := []tasks.Arg{
		{
			Name:  "UUID",
			Type:  "string",
			Value: uuid,
		},
		{
			Name:  "Symbol",
			Type:  "string",
			Value: crypt.GetCryptDataConfig().Symbol,
		},
		{
			Name:  "Branch",
			Type:  "string",
			Value: body.Branch,
		},
		{
			Name:  "Env",
			Type:  "string",
			Value: string(env),
		},
		{
			Name:  "BeforeScript",
			Type:  "[]string",
			Value: params.BeforeScript,
		},
		{
			Name:  "Script",
			Type:  "[]string",
			Value: params.Script,
		},
		{
			Name:  "AfterScript",
			Type:  "[]string",
			Value: params.AfterScript,
		},
	}
	// 发送部署任务
	signature := &tasks.Signature{
		UUID: uuid,
		Name: "call",
		Args: args,
		ETA:  &eta,
		// 重试次数和斐波那契间隔
		// RetryCount:   3,
		// RetryTimeout: 15,
	}

	argsString, _ := logr.JSON.Marshal(args)
	tl := types.TaskLog{
		Symbol:    crypt.GetCryptDataConfig().InternalDeloy.Symbol,
		Committer: crypt.GetCryptDataConfig().Message.UserName,
		Version:   string([]byte(crypt.GetCryptDataConfig().Message.After)[:7]),
		Uuid:      uuid,
		CreateAt:  carbon.Now().ToTimestamp(),
		Args:      string(argsString),
		Type:      2,
	}

	if _, err := queue.MachineryServer.SendTask(signature); err != nil {
		tl.State = "FAILURE"
		sql.GetLiteInstance().Create(&tl)

		taskLogrus.Errorf("Failed task delivered，%v", err)
		logr.Clog.Errorf("Failed task delivered，%v", err)

		ctx.JSON(types.Response{
			Code:    400,
			Message: "fail",
			Data:    nil,
		})
	} else {
		tl.State = "PENDING"
		sql.GetLiteInstance().Create(&tl)
		ctx.JSON(types.Response{
			Code:    200,
			Message: "Success task " + uuid + " delivered",
			Data:    nil,
		})
	}
}

func queryRepo(internalDeloy types.InternalDeploy, body ReleaseBody) (CommitRepository, error, types.TaskParams) {
	// 链接ssh
	sshHost := internalDeloy.Auth.Host
	sshUser := internalDeloy.Auth.User
	sshPassword := internalDeloy.Auth.Pwd
	sshType := "password"       //password 或者 key
	sshKeyPath := "id_rsa path" //ssh id_rsa.id 路径"
	sshPort := internalDeloy.Auth.Port

	//创建sshp登陆配置
	conf := &ssh.ClientConfig{
		Timeout:         time.Second,
		User:            sshUser,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	if sshType == "password" {
		conf.Auth = []ssh.AuthMethod{ssh.Password(sshPassword)}
	} else {
		conf.Auth = []ssh.AuthMethod{interactive.PublicKeyAuthFunc(sshKeyPath)}
	}

	//dial 获取ssh client
	addr := fmt.Sprintf("%s:%d", sshHost, sshPort)
	sshClient, err := ssh.Dial("tcp", addr, conf)
	if err != nil {
		logr.Clog.Errorf("创建ssh client 失败", err)
		return CommitRepository{}, err, types.TaskParams{}
	}
	defer sshClient.Close()

	snowflake := carbon.Now().Format("Ymd") + logr.SnowFlakeId()
	var content string
	u, err := url.Parse(body.HtmlUrl)
	if err != nil {
		return CommitRepository{}, errors.New(fmt.Sprintf("仓库地址解析失败：%v", body.HtmlUrl)), types.TaskParams{}
	}

	var (
		resArrs []CommitRepository
		resArr  CommitRepository
	)

	if strings.Contains(body.HtmlUrl, "gitee.com") {
		// gitee 需要decode
		// base64.StdEncoding.DecodeString()
		// https://gitee.com/api/v5/repos/marksirl/go-runner/contents/.runner-ci.yml?access_token=cb2aebbcd7449f7e25bc950ccf1b0106&ref=master
		//repo := strings.Split(u.Path, "/")[1]
		//pojoName := strings.Trim(strings.Split(u.Path, "/")[2], ".git")

		// https://gitee.com/api/v5/repos/marksirl/go-runner/commits?access_token=cb2aebbcd7449f7e25bc950ccf1b0106&ref=master&page=1&per_page=1
		//commitInfoUrl := fmt.Sprintf("%s://%s/api/v5/repos/%s/%s/commits?access_token=%s&sha=%s&page=1&per_page=1", u.Scheme, u.Hostname(), body.Owner, body.Repo, internalDeloy.Token, body.Branch)
		//commitRepository, err := logr.RequestGet(commitInfoUrl, types.RequestHeader{
		//	Name:  "PRIVATE-TOKEN",
		//	Value: internalDeloy.Token,
		//}, false)
		//if err != nil {
		//	return CommitRepository{}, err, types.TaskParams{}
		//}
		//err = logr.JSON.Unmarshal([]byte(commitRepository), &resArrs)
		//if err != nil {
		//	return CommitRepository{}, err, types.TaskParams{}
		//}
		//
		//if len(resArrs) > 0 {
		//	resArr = resArrs[0]
		//	urlArr := strings.Split(resArr.CommentsUrl, "/")
		//	if len(urlArr) > 9 {
		//		resArr.UserName = urlArr[6]
		//		resArr.After = urlArr[9]
		//	} else {
		//		return CommitRepository{}, errors.New(fmt.Sprintf("提交信息获取失败: %v", resArr.CommentsUrl)), types.TaskParams{}
		//	}
		//} else {
		//	return CommitRepository{}, errors.New(fmt.Sprintf("提交信息获取失败: %v", commitInfoUrl)), types.TaskParams{}
		//}
	} else if strings.Contains(body.HtmlUrl, "github.com") {
		// github 需要decode
		// base64.StdEncoding.DecodeString()
		// https://api.github.com/repos/zrone/JDproject/contents/.gitignore?ref=master
		//repo := strings.Split(u.Path, "/")[1]
		//pojoName := strings.Trim(strings.Split(u.Path, "/")[2], ".git")

		// https://api.github.com/repos/zrone/JDproject/commits?sha=master&page=1&per_page=1
		//commitInfoUrl := fmt.Sprintf("%s://%s/repos/%s/%s/commits?sha=%s&page=1&per_page=1", u.Scheme, u.Hostname(), body.Owner, body.Repo, body.Branch)
		//commitRepository, err := logr.RequestGet(commitInfoUrl, types.RequestHeader{
		//	Name:  "Authorization",
		//	Value: internalDeloy.Token,
		//}, false)
		//if err != nil {
		//	return CommitRepository{}, err, types.TaskParams{}
		//}
		//err = logr.JSON.Unmarshal([]byte(commitRepository), &resArrs)
		//if err != nil {
		//	return CommitRepository{}, err, types.TaskParams{}
		//}
		//
		//if len(resArrs) > 0 {
		//	resArr = resArrs[0]
		//	urlArr := strings.Split(resArr.CommentsUrl, "/")
		//	if len(urlArr) > 9 {
		//		resArr.UserName = urlArr[6]
		//		resArr.After = urlArr[9]
		//	} else {
		//		return CommitRepository{}, errors.New(fmt.Sprintf("提交信息获取失败: %v", resArr.CommentsUrl)), types.TaskParams{}
		//	}
		//} else {
		//	return CommitRepository{}, errors.New(fmt.Sprintf("提交信息获取失败: %v", commitInfoUrl)), types.TaskParams{}
		//}
	} else {
		// gitlab
		// crypt.InternalDeloy.ProjectId
		// crypt.InternalDeloy.Token
		// http://repository.yindadianzi.com/api/v4/projects/12/repository/files/.runner-ci.yml/raw?ref=prod&private_token=8Yfmt5mu_DwXyM1Xum8H

		requestUrl := fmt.Sprintf("%s://%s/api/v4/projects/%d/repository/files/.runner-ci.yml/raw?ref=%s", u.Scheme, u.Hostname(), internalDeloy.ProjectId, body.Branch)
		response, err := logr.RequestGet(requestUrl, types.RequestHeader{
			Name:  "PRIVATE-TOKEN",
			Value: internalDeloy.Token,
		}, false)
		if err != nil {
			return CommitRepository{}, err, types.TaskParams{}
		}
		content = response
		// http://repository.yindadianzi.com/api/v4/projects/12/repository/commits?ref_name=wangpeng&page=1&per_page=1
		commitInfoUrl := fmt.Sprintf("%s://%s/api/v4/projects/%d/repository/commits?ref_name=%s&page=1&per_page=1", u.Scheme, u.Hostname(), internalDeloy.ProjectId, body.Branch)
		commitRepository, err := logr.RequestGet(commitInfoUrl, types.RequestHeader{
			Name:  "PRIVATE-TOKEN",
			Value: internalDeloy.Token,
		}, false)
		if err != nil {
			return CommitRepository{}, err, types.TaskParams{}
		}
		err = logr.JSON.Unmarshal([]byte(commitRepository), &resArrs)
		if err != nil {
			return CommitRepository{}, err, types.TaskParams{}
		}
		fmt.Println(resArrs)
		if len(resArrs) > 0 {
			resArr = resArrs[0]
		} else {
			return CommitRepository{}, errors.New(fmt.Sprintf("提交信息获取失败: %v", commitInfoUrl)), types.TaskParams{}
		}
	}

	dir, _ := os.Getwd()
	cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf(`touch %s/runtime/git/%s.runner-ci.yml`, dir, snowflake))
	cmd.Start()
	cmd.Wait()

	// 内容写入文件
	f, err := os.OpenFile(fmt.Sprintf("%s/runtime/git/%s.runner-ci.yml", dir, snowflake), os.O_APPEND|os.O_WRONLY, os.ModeAppend)
	if err != nil {
		logr.Clog.Errorf("open file error :", err)
		return CommitRepository{}, err, types.TaskParams{}
	}
	// 关闭文件
	defer f.Close()
	_, err = f.WriteString(content)
	if err != nil {
		logr.Clog.Errorf("文件写入失败 :", err)
		return CommitRepository{}, err, types.TaskParams{}
	}

	var (
		runnerCi types.RunnerCi
	)

	config.ParseYaml(fmt.Sprintf("runtime/git/%s.runner-ci.yml", snowflake), &runnerCi)
	var (
		taskParam types.TaskParams = types.TaskParams{
			Environment:  runnerCi.Environment,
			BeforeScript: runnerCi.Prepare,
			Script:       runnerCi.Script,
			AfterScript:  runnerCi.Release,
		}
	)

	return resArr, nil, taskParam
}

// 项目 ssh 配置
func BuildProjConfigure(symbol string) error {

	userDir, _ := homedir.Dir()
	sshKey := fmt.Sprintf(`%s/.ssh/id_rsa.pub`, userDir)
	_, err := os.Stat(sshKey)
	if err != nil {
		// 生成 sshKey
		child, err := gexpect.Spawn("ssh-keygen -t rsa")
		if err != nil {
			panic(err)
		}
		err = child.Expect("id_rsa):")
		if err == nil {
			child.SendLine("")
		}
		err = child.Expect("Enter passphrase (empty for no passphrase):")
		if err == nil {
			child.SendLine("")
		}
		err = child.Expect("Enter same passphrase again:")
		if err == nil {
			child.SendLine("")
		}

		child.Interact()
		child.Close()
	}

	var ext types.InternalDeploy
	sql.GetLiteInstance().Where("symbol = ?", symbol).Take(&ext)
	if ext == (types.InternalDeploy{}) {
		return errors.New(types.UNKNOWN_SYMBOL)
	}
	authorized_keys(sshKey, ext)
	return nil
}

func authorized_keys(sshKey string, internalDeloy types.InternalDeploy) {
	// 第一次免确认
	child, err := gexpect.Spawn(fmt.Sprintf(`ssh-copy-id -i %s %s@%s -p %d`, sshKey, internalDeloy.Auth.User, internalDeloy.Auth.Host, internalDeloy.Auth.Port))
	if err != nil {
		panic(err)
	}
	err = child.ExpectTimeout("Are you sure you want to continue connecting (yes/no/[fingerprint])?", time.Second*5)
	var symbol bool = false
	if err == nil {
		symbol = true
		child.SendLine("yes")
	}

	if !symbol {
		child.Close()
		child, _ = gexpect.Spawn(fmt.Sprintf(`ssh-copy-id -i %s %s@%s -p %d`, sshKey, internalDeloy.Auth.User, internalDeloy.Auth.Host, internalDeloy.Auth.Port))
	}
	err = child.ExpectTimeout("password:", time.Second*5)
	if err == nil {
		child.SendLine(internalDeloy.Auth.Pwd)
	}
	child.Interact()
	child.Close()
}
